<?php
namespace WDB\Query;
use WDB,
    ArrayAccess,
    Iterator,
    Countable;

/**
 *
 * @author Richard Ejem <richard(at)ejem.cz>
 * @package WDB
 * 
 * @property-read \WDB\Query\Select $query
 * @property-read array $array
 */
class SelectedRow extends WDB\BaseObject implements ArrayAccess, Iterator, Countable
{
    /**@var iSelectResult*/
    private $result;
    
    /**@var array*/
    private $row;
    
    /**@var array in form of column nubmer=>column name*/
    private $colNumberMap;
    
    /**@return iSelectResult*/
    public function getResult()
    {
        return $this->result;
    }
    /**@return WDB\Query\Select*/
    public function getQuery()
    {
        return $this->result->query;
    }
    
    /**
     *
     * @param iSelectResult result object
     * @param array row data
     */
    public function __construct(iSelectResult $result, array $row)
    {
        $this->row = $row;
        $this->result = $result;
        $this->colNumberMap = $this->getColNumberMapping($row);
    }
    
    /**
     * Create column number mapping for an associative array.
     * 
     * When a column is accessed through number, the corresponding key
     * is mappedd by this array.
     *
     * @param array associative array from fetch_assoc
     * @return array mapping in form of "row number"=>"row name"
     */
    private function getColNumberMapping(array $row)
    {
        return array_keys($row);
    }
    
    /**
     * Get the result as an associative array.
     *
     * @return array
     */
    public function getArray()
    {
        return $this->row;
    }
    
    /**
     * Retrieves a query to retrieve record bound to this row by a particular foreign key.
     *
     * @param string $constraint name of FK constraint (higher priority) or column
     * @return SelectedRow
     * 
     * @throws WDB\Exception\QueryException
     */
    public function foreign($constraint)
    {
        if (!isset($this->result->foreignKeys[$constraint]))
        {
            $found = FALSE;
            foreach ($this->result->foreignKeys as $cname=>$key)
            {
                if (count($key->columns) == 1 && $key->columns[0] == $constraint)
                {
                    $constraint = $cname;
                    $found = TRUE;
                    break;
                }
            }
            if (!$found) throw new WDB\Exception\KeyException ("foreign key constraint or column $constraint not found");
        }
        $foreignKey = $this->result->foreignKeys[$constraint];
        $filter = array();
        foreach ($foreignKey->refColumns as $colId=>$colName)
        {
            $filter[$colName] = $this[$foreignKey->columns[$colId]];
        }
        $query = new Select(Element\ColumnIdentifier::ALL_COLUMNS, new Element\TableIdentifier($foreignKey->refTable, $foreignKey->refSchema));
        $query->filter($filter);
        return $query->run($this->getQuery()->getDatabase())->singleRow();
    }
    
    // <editor-fold defaultstate="collapsed" desc="\Countable imp.">
    /**
     * Returns count of fields in this row.
     *
     * @return int
     */
    public function count()
    {
        return count($this->row);
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="\ArrayAccess imp.">
    /**
     * Checks if column with such name or number exists.
     *
     * @param string|int column name or number
     * @return bool
     */
    public function offsetExists($offset)
    {
        return array_key_exists($offset, $this->row) || isset($this->colNumberMap[$offset]);
    }

    /**
     * Return value of the specified column.
     *
     * @param string|int column name or number
     * @return mixed
     */
    public function offsetGet($offset)
    {
        if (!$this->offsetExists($offset)) throw new \WDB\Exception\OutOfBounds("offset $offset does not exist");
        return array_key_exists($offset, $this->row) ? $this->row[$offset] : $this->row[$this->colNumberMap[$offset]];
    }

    /**
     * Set value of the specified column.
     *
     * @param string|int column name or number
     * @param mixed
     */
    public function offsetSet($offset, $value)
    {
        if (isset($this->colNumberMap[$offset]))
        {
            $this->row[$this->colNumberMap[$offset]] = $value;
        }
        else
        {
            $this->row[$offset] = $value;
        }
    }

    /**
     * Nullate specified column.
     *
     * @param string|int column name or number
     */
    public function offsetUnset($offset)
    {
        $this->offsetSet($offset, NULL);
        //actual unset is not available - key should remain present
        //because the column still exists
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="\Iterator impl.">
    /**
     * Return value on the position of internal pointer.
     *
     * @return mixed
     */
    public function current()
    {
        return \current($this->row);
    }

    /**
     * Return column name on the position of internal pointer.
     *
     * @return string
     */
    public function key()
    {
        return \key($this->row);
    }

    /**
     * Increase internal pointer and return value on the new position
     *
     * @return mixed
     */
    public function next()
    {
        return \next($this->row);
    }

    /**
     * Reset the internal pointer
     */
    public function rewind()
    {
        return \reset($this->row);
    }

    /**
     * Return true if current position of internal pointer is valid (not after last element)
     *
     * @return bool
     */
    public function valid()
    {
        $key = key($this->row);
        return ($key !== NULL && $key !== FALSE);
    }
    
    // </editor-fold>
    
    /**
     * dedicated for debugging.
     * 
     * simply prints out row data.
     * 
     * @param bool html output (plaintext otherwise)
     * @param bool print (return otherwise)
     */
    public function dump($html = true, $print = true)
    {
        if ($html)
        {
            $data = "<table>\n";
            foreach ($this->row as $key=>$val)
            {
                $data .= '<tr><th>'.htmlspecialchars($key).'</th><td>'.htmlspecialchars(var_export($val, true))."</td></tr>\n";
            }
            $data .= "</table>\n";
        }
        else
        {
            $data = '';
            foreach ($this->row as $key=>$val)
            {
                $data .= $key.': '.var_export($val, true)."\n";
            }
        }
        if ($print)
        {
            echo $data;
            return '';
        }
        else
        {
            return $data;
        }
    }
}